#include "Residue.h"


Residue::Residue(const Integer &n, const Integer &mod)
{
	if (mod <= 1)
		throw;

	this->num = n % mod;
	this->mod = mod;
}

const Integer& Residue::Value() const
{
	return this->num;
}

const Integer& Residue::Modulus() const
{
	return this->mod;
}

std::string Residue::str() const
{
	return this->num.str();
}

std::ostream& operator<<(std::ostream &out, const Residue &n)
{
	return out << n.str();
}

bool IsSameSystem(const Residue &m, const Residue &n)
{
	return m.mod == 0 || n.mod == 0 || m.mod == n.mod;
}

bool operator==(const Residue &lhs, const Residue &rhs)
{
	if (lhs.mod == 0 && rhs.mod == 0)
		return lhs.num == rhs.num;
	else if (lhs.mod == 0)
		return lhs.num % rhs.mod == rhs.num;
	else if (rhs.mod == 0)
		return rhs.num % lhs.mod == lhs.num;
	else
		return lhs.mod == rhs.mod && lhs.num == rhs.num;
}

bool operator!=(const Residue &lhs, const Residue &rhs)
{
	return !(lhs == rhs);
}

bool operator<(const Residue &lhs, const Residue &rhs)
{
	if (!IsSameSystem(lhs, rhs))
		throw;

	if (!((lhs.mod == 0) ^ (rhs.mod == 0)))
		return lhs.num < rhs.num;
	else if (lhs.mod == 0)
		return lhs.num % rhs.mod < rhs.num;
	else
		return rhs.num % lhs.mod > lhs.num;
}

bool operator<=(const Residue &lhs, const Residue &rhs)
{
	return !(rhs > lhs);
}

bool operator>(const Residue &lhs, const Residue &rhs)
{
	return rhs < lhs;
}

bool operator>=(const Residue &lhs, const Residue &rhs)
{
	return !(lhs < rhs);
}

Residue Residue::operator-() const
{
	return Residue(-this->num, this->mod);
}

Residue operator+(const Residue &lhs, const Residue &rhs)
{
	if (!IsSameSystem(lhs, rhs))
		throw;

	if (lhs.mod == 0)
	{
		if (rhs.mod == 0)
			return Residue(lhs.num + rhs.num);
		else
			return Residue(lhs.num + rhs.num, rhs.mod);
	}
	else
		return Residue(lhs.num + rhs.num, lhs.mod);
}

Residue& Residue::operator+=(const Residue &addend)
{
	if (!IsSameSystem(*this, addend))
		throw;

	if (this->mod == 0)
	{
		if (addend.mod == 0)
		{
			this->num += addend.num;
			return *this;
		}
		else
			return *this = Residue(this->num + addend.num, addend.mod);
	}
	else
	{
		this->num = (this->num + addend.num) % this->mod;
		return *this;
	}
}

Residue& Residue::operator++()
{
	return *this += 1;
}

Residue Residue::operator++(int)
{
	Residue n(*this);
	*this += 1;
	return n;
}

Residue operator-(const Residue &lhs, const Residue &rhs)
{
	if (!IsSameSystem(lhs, rhs))
		throw;

	if (lhs.mod == 0)
	{
		if (rhs.mod == 0)
			return Residue(lhs.num - rhs.num);
		else
			return Residue(lhs.num - rhs.num, rhs.mod);
	}
	else
		return Residue(lhs.num - rhs.num, lhs.mod);
}

Residue& Residue::operator-=(const Residue &subtrahend)
{
	if (!IsSameSystem(*this, subtrahend))
		throw;

	if (this->mod == 0)
	{
		if (subtrahend.mod == 0)
		{
			this->num -= subtrahend.num;
			return *this;
		}
		else
			return *this = Residue(this->num - subtrahend.num, subtrahend.mod);
	}
	else
	{
		this->num = (this->num - subtrahend.num) % this->mod;
		return *this;
	}
}

Residue& Residue::operator--()
{
	return *this -= 1;
}

Residue Residue::operator--(int)
{
	Residue n(*this);
	*this -= 1;
	return n;
}

Residue operator*(const Residue &lhs, const Residue &rhs)
{
	if (!IsSameSystem(lhs, rhs))
		throw;

	if (lhs.mod == 0)
	{
		if (rhs.mod == 0)
			return Residue(lhs.num * rhs.num);
		else
			return Residue(lhs.num * rhs.num, rhs.mod);
	}
	else
		return Residue(lhs.num * rhs.num, lhs.mod);
}

Residue& Residue::operator*=(const Residue &factor)
{
	if (!IsSameSystem(*this, factor))
		throw;

	if (this->mod == 0)
	{
		if (factor.mod == 0)
		{
			this->num *= factor.num;
			return *this;
		}
		else
			return *this = Residue(this->num * factor.num, factor.mod);
	}
	else
	{
		this->num = (this->num * factor.num) % this->mod;
		return *this;
	}
}

Residue Square(const Residue &n)
{
	if (n.mod == 0)
		return Square(n.num);
	else
		return Residue(Square(n.num), n.mod);
}
